#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <mach/io.h>
#include <linux/io.h>
#include <mach/fh_predefined.h>
#include "chip.h"
#include "../soc.h"

static DEFINE_SPINLOCK(scu_lock);

#define PHYS_TO_OFFSET(addr) ({ \
if ((addr) > 0x2d300000) { \
	pr_err("%s: bad addr 0x%x\n", __func__, (addr));\
	dump_stack(); };\
(addr) & 0xff000000 ? SYS_REG_P2V_OFF(addr) : (addr); \
})

void fh_pmu_set_reg(unsigned int offset, unsigned int data)
{
	unsigned long flag;

	offset = PHYS_TO_OFFSET(offset);

	spin_lock_irqsave(&scu_lock, flag);
	writel(data, (void __iomem *)VA_SYS_REG_BASE + offset);
	spin_unlock_irqrestore(&scu_lock, flag);
}
EXPORT_SYMBOL(fh_pmu_set_reg);

unsigned int fh_pmu_get_reg(unsigned int offset)
{
	offset = PHYS_TO_OFFSET(offset);
	return readl((void __iomem *)VA_SYS_REG_BASE + offset);
}
EXPORT_SYMBOL(fh_pmu_get_reg);

void fh_pmu_set_reg_m(unsigned int offset, unsigned int data, unsigned int mask)
{
	offset = PHYS_TO_OFFSET(offset);
	fh_pmu_set_reg(offset, (fh_pmu_get_reg(offset) & (~(mask))) |
				       ((data) & (mask)));
}
EXPORT_SYMBOL(fh_pmu_set_reg_m);

void fh_pmu_set_stmautostopgpio(int stmid, int gpio)
{
	unsigned int val;

	val = fh_pmu_get_reg(REG_CPU_SYS_MTX_CTRL0);

	if (stmid) {
		val &= (~0xfc00);
		val |= gpio<<10;
	} else {
		val &= (~0x3f0);
		val |= gpio<<4;
	}
	fh_pmu_set_reg(REG_CPU_SYS_MTX_CTRL0, val);
}
EXPORT_SYMBOL_GPL(fh_pmu_set_stmautostopgpio);

unsigned long long fh_get_pts64(void)
{
	unsigned long lo, hi;

	fh_pmu_set_reg(REG_PTS_UPDATE, 0);
	hi = fh_pmu_get_reg(REG_PTS_RD_HIGH);
	lo = fh_pmu_get_reg(REG_PTS_RD_LOW);
	return ((u64)lo|((u64)hi)<<32);
}
EXPORT_SYMBOL(fh_get_pts64);

void fh_pmu_arxc_reset(unsigned long phy_addr)
{
	unsigned int arc_addr;
	unsigned long rst_val = 0;
	unsigned long rst_msk = 0;

	/*ARC Reset*/
	fh_pmu_set_reg_m(REG_ARC600_0_CTRL, ARC_SOFT_RST, ARC_SOFT_RST);

	/* Configure ARC Bootcode start address */
	arc_addr = ((phy_addr & 0xffff) << 16) | (phy_addr >> 16);
	fh_pmu_set_reg(REG_ARC600_0_BOOT0, 0x7940266B);
	fh_pmu_set_reg(REG_ARC600_0_BOOT1, arc_addr);
	fh_pmu_set_reg(REG_ARC600_0_BOOT2, 0x0F802020);
	fh_pmu_set_reg(REG_ARC600_0_BOOT3, arc_addr);

	/*config ARC CLK*/
	fh_pmu_set_reg_m(REG_CPU_SYS_CLK_CTRL,
			CKG_ARC600_AUTO_GATE_SEL, CKG_ARC600_AUTO_GATE_SEL);

	/* enable clk, config ARC RESET*/
	rst_val |= ARC0_CTRL_CPU_START;
	rst_val |= ARC0_START_A;
	rst_val |= ARC600_0_EN;
	rst_msk = ARC0_CTRL_CPU_START | ARC0_START_A | ARC600_0_EN;
	fh_pmu_set_reg_m(REG_ARC600_0_CTRL, rst_val, rst_msk);
}
EXPORT_SYMBOL_GPL(fh_pmu_arxc_reset);

void fh_pmu_arxc_kickoff(void)
{
	/*release RESET*/
	fh_pmu_set_reg_m(REG_ARC600_0_CTRL, 0, ARC_SOFT_RST);
}
EXPORT_SYMBOL_GPL(fh_pmu_arxc_kickoff);

void fh_get_chipid(unsigned int *plat_id, unsigned int *chip_id)
{
	unsigned int _plat_id = 0;

	_plat_id = fh_pmu_get_reg(REG_PMU_CHIP_ID);
	if (plat_id != NULL)
		*plat_id = _plat_id;

	if (chip_id != NULL)
		*chip_id = fh_pmu_get_reg(REG_PKG_ID);
}
EXPORT_SYMBOL(fh_get_chipid);

unsigned int fh_pmu_get_ddrsize(void)
{
	return fh_pmu_get_reg(REG_PMU_DDR_SIZE);
}
EXPORT_SYMBOL_GPL(fh_pmu_get_ddrsize);

void fh_pmu_perfmon_init(int timelow, int timehigh)
{
	/* enable perfmon0~5 clk, busmon timer clk */
	fh_pmu_set_reg_m(REG_DMC_CLK_CTRL, (0x001000fe), (0x001000fe));
	fh_pmu_set_reg_m(REG_DFIMON_TMR1_CTRL0, (0x3), (0x3));
	fh_pmu_set_reg_m(REG_DFIMON_TMR1_CTRL0, (0x1), (0x3));

	//high time
	fh_pmu_set_reg(REG_HIGH_LEN_TIMER1, timehigh);
	//low time
	fh_pmu_set_reg(REG_LOW_LEN_TIMER1, timelow);
	/* perf_trigger_sel */
	fh_pmu_set_reg_m(REG_PERF_TRIGGER, 0x2, 0x2);
}
EXPORT_SYMBOL(fh_pmu_perfmon_init);

void fh_pmu_perfmon_timer_cnt_clr(void)
{
	fh_pmu_set_reg_m(REG_DFIMON_TMR1_CTRL0, 0x0, 0x3);
	fh_pmu_set_reg_m(REG_DFIMON_TMR1_CTRL0, 0x1, 0x3);
}
EXPORT_SYMBOL(fh_pmu_perfmon_timer_cnt_clr);

void fh_pmu_perfmon_rd_trigger(void)
{
	/* perf_rd_trigger */
	fh_pmu_set_reg_m(REG_PERF_TRIGGER, 0x0, 0x1);
	fh_pmu_set_reg_m(REG_PERF_TRIGGER, 0x1, 0x1);
}
EXPORT_SYMBOL(fh_pmu_perfmon_rd_trigger);

void fh_pmu_set_mclk(int freq)
{
	fh_pmu_set_reg_m(REG_CKG_I2S0_CTL, 0x2, 0x3); // i2s_clk=648M
	fh_pmu_set_reg(REG_IIS0_CLK_CTRL, 0);
	fh_pmu_set_reg(REG_CKG_I2S_FRAC_DIV_N, I2S_CLK_FREQ/1600);
	fh_pmu_set_reg(REG_CKG_I2S_FRAC_DIV_M, freq/800);

	fh_pmu_set_reg(REG_IIS0_CLK_CTRL, CKG_I2S_FRAC_DIV_EN|CKG_I2S_FAST_EN);
}
EXPORT_SYMBOL(fh_pmu_set_mclk);

static void _reset_ip(int offset, int bit)
{
	fh_pmu_set_reg_m(offset, BIT(bit), BIT(bit));
	udelay(1);
	fh_pmu_set_reg_m(offset, 0, BIT(bit));
}

static void _reset_ip_wc(int offset, int bit)
{
	fh_pmu_set_reg_m(offset, BIT(bit), BIT(bit));
	while (fh_pmu_get_reg(offset) & BIT(bit))
		;
}

void fh_pmu_reset_i2s(void)
{
	_reset_ip(REG_AP_PERI_SOFT_RST0, I2S0_SOFT_RST);
}
EXPORT_SYMBOL(fh_pmu_reset_i2s);

void fh_pmu_usb_utmi_rst(void)
{
	unsigned int val;
	void __iomem *addr = ioremap(0x1c000000, 0x100000);

	val = readl(addr + 0x14);
	val |= (1 << 13);
	writel(val, addr + 0x14);

	mdelay(1);

	val &= ~(1 << 13);
	writel(val, addr + 0x14);

	mdelay(20);

	iounmap(addr);
}
EXPORT_SYMBOL_GPL(fh_pmu_usb_utmi_rst);

void fh_pmu_usb_phy_rst(void)
{
	unsigned int val;
	void __iomem *addr = ioremap(0x1ca00000, 0x100000);

	val = readl(addr + 0x18);
	val &= ~(1 << 11);			//IDDQ
	val &= ~(1 << 31);			//OTGDISABLE
	val |= (1 << 20);			//COMMONONN
	writel(val, addr + 0x18);

	mdelay(1);

	val = readl(addr + 0x20);
	val |= 0xC0000000;
	writel(val, addr + 0x20);

	mdelay(1);

	val &= ~0xC0000000;
	writel(val, addr + 0x20);

	iounmap(addr);
}
EXPORT_SYMBOL_GPL(fh_pmu_usb_phy_rst);

void fh_pmu_usb_resume(void)
{
	unsigned int val;
	void __iomem *addr = ioremap(0x1ca00000, 0x100000);

	val = readl(addr + 0x18);
	val |= (1 << 6);
	writel(val, addr + 0x18);

	mdelay(1);

	iounmap(addr);
}
EXPORT_SYMBOL_GPL(fh_pmu_usb_resume);

void fh_pmu_usb_tune(void)
{
	unsigned int val;
	void __iomem *addr = ioremap(0x1ca00000, 0x100000);

	val = readl(addr + 0x1c);
	val &= ~(0x7 << 18);
	val |= ((USB_TUNE_ADJ_SET & (0x7 << 0)) << (18 - 0));	//COMPDIS
	val &= ~(0x7 << 21);
	val |= ((USB_TUNE_ADJ_SET & (0x7 << 8)) << (21 - 8));	//SQRX
	val &= ~(0x7 << 26);
	val |= ((USB_TUNE_ADJ_SET & (0x7 << 4)) << (26 - 4));	//OTG
	writel(val, addr + 0x1c);

	val = readl(addr + 0x20);
	val &= ~(0xF << 0);
	val |= ((USB_TUNE_ADJ_SET & (0xF << 12)) >> (12 - 0));	//TXFSLS
	val &= ~(0xF << 4);
	val |= ((USB_TUNE_ADJ_SET & (0xF << 24)) >> (24 - 4));	//TXVREF
	val &= ~(0x3 << 8);
	val |= ((USB_TUNE_ADJ_SET & (0x3 << 20)) >> (20 - 8));	//TXRISE
	val &= ~(0x3 << 10);
	val |= ((USB_TUNE_ADJ_SET & (0x3 << 30)) >> (30 - 10));	//TXRES
	val &= ~(0x3 << 12);
	val |= ((USB_TUNE_ADJ_SET & (0x3 << 16)) >> (16 - 12));	//TXPREEMPAMP
	val &= ~(0x1 << 14);
	val |= ((USB_TUNE_ADJ_SET & (0x1 << 18)) >> (18 - 14));	//TXPREEMPPULSE
	writel(val, addr + 0x20);

	val = readl(addr + 0x1c);
	val &= ~(0x3 << 29);
	val |= ((USB_TUNE_ADJ_SET & (0x3 << 28)) << (29 - 28));	//TXHSXV
	writel(val, addr + 0x1c);

	iounmap(addr);
}
EXPORT_SYMBOL_GPL(fh_pmu_usb_tune);

void fh_pmu_usb_vbus_vldext(void)
{
	unsigned int val;
	void __iomem *addr = ioremap(0x1ca00000, 0x100000);

	val = readl(addr + 0x1c);
	val |= (1 << 2);
	val |= (1 << 3);
	writel(val, addr + 0x1c);

	iounmap(addr);
}
EXPORT_SYMBOL_GPL(fh_pmu_usb_vbus_vldext);

void fh_pmu_sdc_reset(int slot_id)
{
}
EXPORT_SYMBOL_GPL(fh_pmu_sdc_reset);

void fh_pmu_restart(void)
{
	fh_pmu_set_reg(REG_WR_PROTECT, 0x5a5a5a5a);
	fh_pmu_set_reg(REG_GLB_RESET, SW_GLB_RST);
}

int fh_pmu_init(void)
{

	spin_lock_init(&scu_lock);

	return 0;
}
